#include "buffer.h"
#include <string.h>
#include <stdio.h>

int luaopen_buffer(lua_State * L)
{
	luaL_reg lib_buffer[]=
	{
		{"create",&buffer::Create},
		{"ptrtostring",&buffer::PtrToString},
		{NULL,NULL},
	};
	luaL_register(L,"buffer",lib_buffer);
	buffer::Reg(L);
	return 0;
}

int buffer::Create(lua_State * L)
{
	size_t sz=luaL_optint(L,1,0);
	buffer * ud=buffer::Push(L);
	ud->sz=sz;
	if(ud->sz>0)
	{
		ud->ptr=new byte[ud->sz];
		if(ud->ptr)
			memset(ud->ptr,0,ud->sz);
		else
			return luaL_error(L,"Memory allocation failed.");
	}
	else
	{
		ud->sz=0;
		ud->ptr=0;
	}
	return 1;
}

int buffer::PtrToString(lua_State * L)
{
	const char * data=(const char *)lua_touserdata(L,1);
	if(data)
	{
		lua_pushlstring(L,(const char *)data,luaL_checkint(L,2));
		return 1;
	}
	else return 0;
	return 0;
}

void buffer::Reg(lua_State * L)
{
	luaL_Reg methods[]=
	{
		{"set",&buffer::Set},
		{"get",&buffer::Get},
		{"tostring",&buffer::ToString},
		{"ptr",&buffer::ToPtr},
		{"size",&buffer::Size},
		{"release",&buffer::Release},
		{"allocate",&buffer::Allocate},
		{"flipimage",&buffer::FlipImage},
		{NULL,NULL},
	};
	luaL_Reg metamethods[]=
	{
		{NULL,NULL},
	};
	luaL_openlib(L,TYPENAME_BUFFER,methods,0);
	luaL_newmetatable(L,TYPENAME_BUFFER);
	luaL_openlib(L,0,metamethods,0);
	lua_pushliteral(L,"__index");
	lua_pushvalue(L,-3);
	lua_rawset(L,-3);
	lua_pushliteral(L,"__metatable");
	lua_pushvalue(L,-3);
	lua_rawset(L,-3);
	lua_pop(L,2);
}

buffer * buffer::Push(lua_State * L)
{
	buffer * ud=(buffer *)lua_newuserdata(L,sizeof(buffer));
	luaL_getmetatable(L,TYPENAME_BUFFER);
	lua_setmetatable(L,-2);
	return ud;
}

int buffer::Set(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	size_t addr=(size_t)luaL_checkint(L,2);
	byte value=(byte)luaL_checkint(L,3);
	if(!ud->ptr) return luaL_error(L,"Buffer has been released.");
	if(addr<0||addr>=ud->sz) return luaL_error(L,"Index out of range");
	(ud->ptr)[addr]=value;
	return 0;
}

int buffer::Get(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	size_t addr=(size_t)luaL_checkint(L,2);
	if(!ud->ptr) return luaL_error(L,"Buffer has been released.");
	if(addr<0||addr>=ud->sz) return luaL_error(L,"Index out of range");
	lua_pushnumber(L,(ud->ptr)[addr]);
	return 1;
}

int buffer::ToString(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	if(ud->ptr) lua_pushlstring(L,(const char *)ud->ptr,ud->sz);
	else lua_pushnil(L);
	return 1;
}

int buffer::ToPtr(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	if(ud->ptr) lua_pushlightuserdata(L,(void *)ud->ptr);
	else lua_pushnil(L);
	return 1;
}

int buffer::Size(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	lua_pushnumber(L,ud->sz);
	return 1;
}

int buffer::Allocate(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	if(ud->ptr)
		return luaL_error(L,"Buffer has been allocated.");
	else
	{
		ud->sz=luaL_checkint(L,2);
		if(ud->sz>0)
		{
			ud->ptr=new byte[ud->sz];
			if(ud->ptr)
				memset(ud->ptr,0,ud->sz);
			else
				return luaL_error(L,"Memory allocation failed.");
		}
		else
		{
			ud->sz=0;
			ud->ptr=0;
		}
	}
	return 0;
}

int buffer::Release(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	if(ud->ptr)
	{
		delete ud->ptr;
		ud->ptr=0;
		ud->sz=0;
	}
	return 0;
}

int buffer::FlipImage(lua_State * L)
{
	buffer * ud=(buffer *)luaL_checkudata(L,1,TYPENAME_BUFFER);
	if(!ud->ptr) return luaL_error(L,"Buffer has been released.");

	size_t w=luaL_checkint(L,2);
	size_t h=luaL_checkint(L,3);
	size_t chan=luaL_checkint(L,4);
	if(ud->sz<w*h*chan) return luaL_error(L,"Not enough bytes in buffer.");

	byte * tmp=new byte[w*h*chan];
	memcpy(tmp,ud->ptr,w*h*chan);

	int xi=0;int xf=w;int yi=0;int yf=h;
	int xs=1;int ys=1;
	int offset=0;

	if(lua_toboolean(L,5)){xi=w-1;xf=-1;xs=-1;}
	if(lua_toboolean(L,6)){yi=h-1;yf=-1;ys=-1;}

	if(!lua_toboolean(L,7)||(w!=h))
	{
		for(int j=yi;j!=yf;j+=ys)
			for(int i=xi;i!=xf;i+=xs)
			{
				memcpy(ud->ptr+(i+j*w)*chan,tmp+offset*chan,chan);
				offset++;
			}
	}
	else
	{
		for(int j=yi;j!=yf;j+=ys)
			for(int i=xi;i!=xf;i+=xs)
			{
				memcpy(ud->ptr+(j+i*w)*chan,tmp+offset*chan,chan);
				offset++;
			}
	}
	delete[] tmp;
	return 0;
}